/** 对axios做一些配置
 * 参考”前端大全”公众号文章
 * GET POST类请求ＵＲＬ末尾一律要加/，否则浏览器会报ＣＯＲＳ类的错，其实后台配置没问题。
 **/
import axios, { AxiosError } from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import qs from "qs";

import { store } from "../store/configureStore";

//
export interface RequestInterceptors {
  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;
  requestInterceptorsCatch?: (err: unknown) => unknown;

  responseInterceptors?: <T = AxiosResponse>(config: T) => T;
  responseInterceptorsCatch?: (err: unknown) => unknown;
}

// 自定义传入的参数
export interface RequestConfig extends AxiosRequestConfig {
  interceptors?: RequestInterceptors;
}

interface CustomRequestConfig<T> extends RequestConfig {
  data?: T;
}

export interface CustomResponse<T> {
  code: number;
  message: string;
  data: T;
  [keys: string]: unknown;
}

// 防抖通过ahooks的useRequest实现了
export interface CancelRequestSource {
  // 这里的key是不固定的，因为我们使用url做index，只有用的时候才知道url，使用此语法解决
  [index: string]: (value: unknown) => void;
}

// 封装成一个类而不是一个函数，因为类可以创建多个实例，适用范围更广，封装性更强
// 拦截器有三种：类拦截器、实例拦截器、接口拦截器
// 拦截器执行顺序：实例请求 --> 类请求 --> 实例响应 --> 类响应
class Request {
  instance: AxiosInstance;

  /*
   * 存放取消方法的集合
   * 在创建请求后将取消请求方法push到该集合中
   * 封装一个方法可以取消请求，传入url: string | string[]
   * 在请求之前判断同一URL是否存在，如果存在就取消请求
   */
  cancelRequestSourceList?: CancelRequestSource[];

  /*
   * 存放所有请求URL的集合
   * 请求之前需要将URL push到该集合中
   * 请求完毕后将url从集合中删除
   * 添加在发送请求之前完成，删除在响应之后删除
   */
  requestURLList?: string[];

  interceptorsObj?: RequestInterceptors;

  constructor(config: RequestConfig) {
    this.instance = axios.create(config);

    this.requestURLList = [];
    this.cancelRequestSourceList = [];

    // 添加请求拦截器
    this.instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        // 添加 token 到请求头
        if (store.getState()?.user?.token && config && config.headers) {
          config.headers.Authorization =
            "Bearer " + store.getState()?.user?.token;
        }

        // 处理 POST 请求头
        if (config.method?.toUpperCase() === "POST" && config.headers) {
          config.headers["Content-Type"] = "application/json";
        }

        return config;
      },
      (err: unknown) => err
    );

    // 添加响应拦截器
    this.instance.interceptors.response.use(
      (res: AxiosResponse) => {
        return res;
      },
      // 错误处理
      (error: AxiosError) => {
        // 根据状态码处理不同错误
        if (error && error.response && error.response.status) {
          switch (error.response.status) {
            case 401:
              window.location.href = "/login";
              return Promise.reject(error.response.data);
            // ... 其他状态码处理
          }
        }
        return Promise.reject(error);
      }
    );
  }

  request<T>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      // 如果我们为单个请求设置拦截器，这里使用单个请求的拦截器
      if (config.interceptors?.requestInterceptors) {
        config = config.interceptors.requestInterceptors(config);
      }

      // const url = config.url;

      this.instance
        .request<T>(config)
        .then((res: any) => {
          // 如果我们为单个响应设置拦截器，在这里使用单个响应的拦截器
          if (config.interceptors?.responseInterceptors) {
            res = config.interceptors.responseInterceptors<T>(res);
          }

          resolve(res);
        })
        .catch((err: AxiosError) => {
          reject(err);
        })
        .finally(() => {
          // console.log(url);
        });
    });
  }
}

let defaultsBaseURL = "";
switch (process.env.NODE_ENV) {
  case "development":
    defaultsBaseURL = "http://127.0.0.1:8001/api/v1/";
    break;
  case "production":
    defaultsBaseURL = "/api/v1/";
    break;
  default:
    defaultsBaseURL = "/api/v1/";
    break;
}

const request = new Request({
  baseURL: defaultsBaseURL,
  timeout: 1000 * 60 * 5,
  validateStatus: (status: number): boolean => {
    return /^(2|3)\d{2}$/.test(status.toString());
  },
  // 请求是否带上cookie，此值居然影响ＣＯＲＳ响应，还有ＵＲＬ末尾的/
  // The value of the 'Access-Control-Allow-Origin' header in the response
  // must not be the wildcard '*' when the request's credentials mode is 'include'.
  // The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
  withCredentials: false,
  interceptors: {
    // 请求拦截器
    requestInterceptors: (config) => {
      console.log("实例请求拦截器");
      return config;
    },
    // 响应拦截器
    responseInterceptors: (result) => {
      console.log("实例响应拦截器");
      return result;
    },
  },
});

/*
 * @interface D 请求参数的interface
 * @interface T 响应结构的interface
 * @param {CustomRequestConfig} config 不管是GET还是POST请求都使用data
 * @return {Promise}
 */
const customRequest = <D, T = unknown>(config: CustomRequestConfig<D>) => {
  const { method = "GET" } = config;
  if (method.toUpperCase() === "GET") {
    config.params = config.data;
  }

  return request.request<CustomResponse<T>>(config);
};

export default customRequest;
